package cn.kosh.zzb.sdk;

import cn.com.jit.new_vstk.AdvanceSignClient;
import cn.com.jit.new_vstk.Bean.SignResult;
import cn.com.jit.new_vstk.exception.NewCSSException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.kosh.zzb.sdk.conf.ZzbConfig;
import cn.kosh.zzb.sdk.exception.ZzbException;
import cn.kosh.zzb.sdk.model.*;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 专资办接口服务
 */
public class ZzbService {
    private final ZzbConfig zzbConfig;
    private SSLSocketFactory sslSocketFactory;

    public ZzbService(ZzbConfig zzbConfig) {
        this.zzbConfig = zzbConfig;
    }

    /**
     * 票房数据上报
     *
     * @param ticketList 影票信息
     * @return 上报结果
     */
    public ZzbResult<?> reportTicket(List<ZzbTicket> ticketList) {
        ReportTicketRequest request = ReportTicketRequest.builder()
                .sendChannelCode(zzbConfig.getChannelCode())
                .ticketList(ticketList)
                .build();
        String url = zzbConfig.getReportUrl() + "/report/reportTicket";
        try (HttpResponse httpResponse = HttpUtil.createPost(url)
                .body(JSONUtil.toJsonStr(request))
                .setSSLSocketFactory(this.getSSLSocketFactory())
                .execute()) {
            String data = httpResponse.body();
            return JSONUtil.toBean(data, ZzbResult.class);
        }
    }

    /**
     * 数据比对文件下载
     *
     * @param startShowDate 开始时间
     * @param endShowDate   结束时间
     * @return 文件流
     */
    public InputStream downloadReportRecord(String startShowDate, String endShowDate) {
        DownloadReportRecordRequest request = DownloadReportRecordRequest.builder()
                .sendChannelCode(zzbConfig.getChannelCode())
                .startShowDate(startShowDate)
                .endShowDate(endShowDate)
                .build();
        request.setSignature(sign(request));
        String url = zzbConfig.getServiceUrl() + StrUtil.format("/data/downloadReportRecord");
        try (HttpResponse httpResponse = HttpUtil.createPost(url)
                .body(JSONUtil.toJsonStr(request))
                .setSSLSocketFactory(this.getSSLSocketFactory())
                .execute()) {
            return httpResponse.bodyStream();
        }
    }

    /**
     * 影片信息下载
     *
     * @param cinemaCode       影院编码
     * @param startPublishDate 开始上映日期
     * @param endPublishDate   结束上映日期
     * @param page             页码
     * @return 影片信息
     */
    public ZzbResult<ZzbFilmPage> downloadFilmInfo(String cinemaCode, String startPublishDate, String endPublishDate, Integer page) {
        DownloadFilmInfoRequest request = DownloadFilmInfoRequest.builder()
                .cinemaCode(cinemaCode)
                .startPublishDate(startPublishDate)
                .endPublishDate(endPublishDate)
                .page(page)
                .build();
        String url = zzbConfig.getServiceUrl() + StrUtil.format("/data/downloadFilmInfo?cinemaCode={}&startPublishDate={}&endPublishDate={}&page={}&signature={}",
                cinemaCode, startPublishDate, endPublishDate, page, sign(request));
        try (HttpResponse httpResponse = HttpUtil.createGet(url)
                .setSSLSocketFactory(this.getSSLSocketFactory())
                .execute()) {
            String data = httpResponse.body();
            return JSONUtil.toBean(data, new TypeReference<ZzbResult<ZzbFilmPage>>() {
            }, false);
        }
    }

    /**
     * 影院信息下载
     *
     * @param cinemaCode 影院编码
     * @return 影院信息
     */
    public ZzbResult<ZzbCinema> getCinemaInfo(String cinemaCode) {
        GetCinemaInfoRequest request = GetCinemaInfoRequest.builder()
                .cinemaCode(cinemaCode)
                .build();
        String url = zzbConfig.getServiceUrl() + StrUtil.format("/data/getCinemaInfo?cinemaCode={}&signature={}",
                cinemaCode, sign(request));
        try (HttpResponse httpResponse = HttpUtil.createGet(url)
                .setSSLSocketFactory(this.getSSLSocketFactory())
                .execute()) {
            String data = httpResponse.body();
            return JSONUtil.toBean(data, new TypeReference<ZzbResult<ZzbCinema>>() {
            }, false);
        }
    }

    /**
     * 影厅信息下载
     *
     * @param cinemaCode 影院编码
     * @return 影厅信息
     */
    public ZzbResult<ZzbCinemaScreen> getScreenInfo(String cinemaCode) {
        GetScreenInfoRequest request = GetScreenInfoRequest.builder()
                .cinemaCode(cinemaCode)
                .build();
        String url = zzbConfig.getServiceUrl() + StrUtil.format("/data/getScreenInfo?cinemaCode={}&signature={}",
                cinemaCode, sign(request));
        try (HttpResponse httpResponse = HttpUtil.createGet(url)
                .setSSLSocketFactory(this.getSSLSocketFactory())
                .execute()) {
            String data = httpResponse.body();
            return JSONUtil.toBean(data, new TypeReference<ZzbResult<ZzbCinemaScreen>>() {
            }, false);
        }
    }


    /**
     * 获取SSLSocketFactory
     */
    private SSLSocketFactory getSSLSocketFactory() {
        if (sslSocketFactory == null) {
            try {
                // 初始化密钥库
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
                KeyStore keyStore = getKeyStore(zzbConfig.getCertFile(), zzbConfig.getCertFilePwd(), "PKCS12");
                keyManagerFactory.init(keyStore, zzbConfig.getCertFilePwd().toCharArray());

                // 初始化信任库
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
                KeyStore trustkeyStore = getKeyStore(zzbConfig.getTrustFile(), zzbConfig.getTrustFilePwd(), "JKS");
                trustManagerFactory.init(trustkeyStore);

                // 初始化SSL上下文
                SSLContext ctx = SSLContext.getInstance("SSL");
                ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
                sslSocketFactory = ctx.getSocketFactory();
            } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException |
                     KeyManagementException | CertificateException | IOException e) {
                throw new ZzbException("证书错误", e);
            }
        }
        return sslSocketFactory;
    }

    /**
     * 获得KeyStore
     */
    private KeyStore getKeyStore(String keyStorePath, String password, String type)
            throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
        try (FileInputStream is = new FileInputStream(keyStorePath)) {
            KeyStore ks = KeyStore.getInstance(type);
            ks.load(is, password.toCharArray());
            return ks;
        }
    }

    /**
     * 签名
     *
     * @param data 签名数据
     * @return 签名结果
     */
    private String sign(Object data) {
        try {
            Map<String, Object> content = new TreeMap<>();
            content.put("data", data);
            content.put("sendChannelCode", zzbConfig.getChannelCode());
            content.put("timestamp", System.currentTimeMillis());
            AdvanceSignClient client = new AdvanceSignClient(zzbConfig.getCssConfigFile());
            SignResult result = client.p7AttachSign(zzbConfig.getCertId(), JSONUtil.toJsonStr(content).getBytes(StandardCharsets.UTF_8));
            byte[] signData = result.getSignData();
            return URLEncodeUtil.encodeAll(Base64.encode(signData), StandardCharsets.UTF_8);
        } catch (NewCSSException e) {
            throw new ZzbException(e.getDescription(), e);
        }
    }

    public static void main(String[] args) throws Exception {
        // 配置信息
        ZzbConfig zzbConfig = ZzbConfig.builder()
                .reportUrl("https://panda.zgdypw.cn:8087/report")
                .serviceUrl("https://panda.zgdypw.cn:8085/service")
                .channelCode("98174065")
                .certId("98174065")
                .certFile("d:/98174065.pfx")
                .certFilePwd("xxxxxx")
                .trustFile("d:/root.truststore")
                .trustFilePwd("20231108")
                .cssConfigFile("d:/cssconfig.properties")
                .build();
        // 影票信息
        ZzbTicket ticket = ZzbTicket.builder()
                .childChannelCode("00000000")
                .cinemaCode("13090401")
                .filmCode("000000252022")
                .netServiceFee(1.47F)
                .numberByDay(1)
                .operation(1)
                .operationDatetime("2023-11-09 16:42:17")
                .parentChannelCode("00000000")
                .saleChannelCode("98151235")
                .screenCode("0000000000000001")
                .screenServiceFee(3.36F)
                .seatCode("88888888010010011101")
                .sessionCode("SE00001234567890")
                .sessionDatetime("2023-11-09 16:31:35")
                .ticketNo("130904010Go0102")
                .ticketPrice(45.00F)
                .build();

        // 时间
        LocalDate now = LocalDate.now();
        String startDate = LocalDateTimeUtil.formatNormal(now.minusDays(30));
        String endDate = LocalDateTimeUtil.formatNormal(now.plusDays(30));

        ZzbService api = new ZzbService(zzbConfig);
        // 票房数据上报
        System.out.println(api.reportTicket(ListUtil.toList(ticket)));
        // 影院信息下载
        System.out.println(JSONUtil.toJsonPrettyStr(api.getCinemaInfo("13065601")));
        // 影厅信息下载
        System.out.println(JSONUtil.toJsonPrettyStr(api.getScreenInfo("13065601")));
        // 影片信息下载
        System.out.println(JSONUtil.toJsonPrettyStr(api.downloadFilmInfo("13065601", startDate, endDate, 1)));
        // 数据比对文件下载
        FileUtil.writeFromStream(api.downloadReportRecord(startDate, endDate), "d:/zzb.zip");
        // 读取比对文件
        try (RandomAccessFile file = new RandomAccessFile("d:/zzb/now.rr", "rw")) {
            for (long i = 0; i < file.length(); i++) {
                file.seek(i);
                System.out.printf("%8d : %b%n", i, file.readBoolean());
            }
        }
        // 测试环境（仅限）可用此接口查询票房数据上报是否成功
        // https://panda.zgdypw.cn:8087/report/reportTest/ticketStatus?reportTime=2023-12-06&traceId=92f4b04dd5aee617
    }
}
